package com.youjia.center.boot.data.mp.basic.service;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.*;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.youjia.center.boot.data.mp.basic.dto.PagedListDto;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;

import static com.youjia.center.boot.data.mp.basic.service.QueryHelper.getUnderLineMap;

/**
 * 扩展mapperService实现
 * @author fengjiansong
 * @date 2022/9/27 18:28
 */
public class BaseServiceImpl<T,E,M extends BaseMapper<E>> implements IBaseService<T,E>{

    protected Log log = LogFactory.getLog(getClass());

    @Autowired
    protected M baseMapper;

    protected Class<E> entityClass;

    protected Class<M> mapperClass = (Class<M>) baseMapper.getClass();

    protected Class<T> dtoClass;

    public BaseServiceImpl() {
        Type[] types = ((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments();
        this.dtoClass = (Class<T>)types[2];
        this.entityClass = (Class<E>)types[1];
    }

    /**
     * 保存dto对象
     *
     * @param t dto
     * @return dto
     */
    @Override
    public T saveDTO(T t) {
        E entity = JSONObject.parseObject(JSONObject.toJSONString(t),entityClass);
        save(entity);
        t = JSONObject.parseObject(JSONObject.toJSONString(entity),dtoClass);
        return t;
    }

    /**
     * 保存或者修改dto对象
     *
     * @param t dto
     * @return dto
     */
    @Override
    public T saveOrUpdateDTO(T t) {
        E entity = JSONObject.parseObject(JSONObject.toJSONString(t),entityClass);
        saveOrUpdate(entity);
        t = JSONObject.parseObject(JSONObject.toJSONString(entity),dtoClass);
        return t;
    }

    /**
     * 保存或者修改dto对象
     *
     * @param t             dto
     * @param updateWrapper updateWrapper
     * @return dto
     */
    @Override
    public T saveOrUpdateDTO(T t, Wrapper<E> updateWrapper) {
        E entity = JSONObject.parseObject(JSONObject.toJSONString(t),entityClass);
        saveOrUpdate(entity, updateWrapper);
        t = JSONObject.parseObject(JSONObject.toJSONString(entity),dtoClass);
        return t;
    }

    /**
     * 批量保存dto
     *
     * @param dtoCollection dto集合
     * @param batchSize     批量提交数量
     * @return 是否成功
     */
    @Override
    public boolean batchSaveDTO(Collection<T> dtoCollection, int batchSize) {
        Collection<E> entityCollection = JSONObject.parseArray(JSONObject.toJSONString(dtoCollection),entityClass);
        return saveBatch(entityCollection,batchSize);
    }

    /**
     * 批量保存或者修改dto
     *
     * @param dtoCollection dto集合
     * @param batchSize     批量提交数量
     * @return 是否成功
     */
    @Override
    public boolean batchSaveOrUpdateDTO(Collection<T> dtoCollection, int batchSize) {
        Collection<E> entityCollection = JSONObject.parseArray(JSONObject.toJSONString(dtoCollection),entityClass);
        return saveOrUpdateBatch(entityCollection,batchSize);
    }

    /**
     * 批量删除dto
     *
     * @param dtoCollection dto集合
     * @param batchSize     批量提交数量
     * @return 是否成功
     */
    @Override
    public boolean batchRemoveDTOByIds(Collection<T> dtoCollection, int batchSize) {
        Collection<E> entityCollection = JSONObject.parseArray(JSONObject.toJSONString(dtoCollection),entityClass);
        return removeBatchByIds(entityCollection, batchSize);
    }

    /**
     * 根据id数组进行删除
     *
     * @param dtoCollection dto集合
     * @return 是否成功
     */
    @Override
    public boolean removeDTOByIds(Collection<T> dtoCollection) {
        Collection<E> entityCollection = JSONObject.parseArray(JSONObject.toJSONString(dtoCollection),entityClass);
        return removeByIds(entityCollection);
    }

    /**
     * 查询全部dto
     *
     * @return dto集合
     */
    @Override
    public List<T> selectDTOList() {
        return JSONObject.parseArray(JSONObject.toJSONString(list()),dtoClass);
    }

    /**
     * 根据主键集合查询列表
     *
     * @param idList 主键集合
     * @return dto集合
     */
    @Override
    public List<T> selectDTOListByIds(Collection<? extends Serializable> idList) {
        return JSONObject.parseArray(JSONObject.toJSONString(listByIds(idList)),dtoClass);
    }

    /**
     * 根据map查询dto集合(参数为驼峰风格名称-自动转换为下划线)
     *
     * @param columnMap 查询map
     * @return dto集合
     */
    @Override
    public List<T> selectDTOListByMap(Map<String, Object> columnMap) {
        Map<String, Object> underLineQueryMap=new HashMap<>();
        getUnderLineMap(columnMap,underLineQueryMap);
        return JSONObject.parseArray(JSONObject.toJSONString(listByMap(underLineQueryMap)),dtoClass);
    }

    /**
     * 根据map查询dto(参数为驼峰风格名称-自动转换为下划线)
     *
     * @param columnMap 查询map
     * @return dto
     */
    @Override
    public T selectDtoOneByMap(Map<String, Object> columnMap) {
        Map<String, Object> underLineQueryMap=new HashMap<>();
        getUnderLineMap(columnMap,underLineQueryMap);
        List<T> list = JSONObject.parseArray(JSONObject.toJSONString(listByMap(underLineQueryMap)),dtoClass);
        if (CollectionUtils.isEmpty(list)){
            return null;
        }
        Assert.isTrue(list.size()==1,"resultList size != 1");
        return list.get(0);
    }

    /**
     * 根据long类型主键查询dto
     *
     * @param id long类型主键
     * @return dto
     */
    @Override
    public T selectDtoOneById(Long id) {
        return JSONObject.parseObject(JSONObject.toJSONString(getById(id)),dtoClass);
    }

    /**
     * 根据String类型主键查询dto
     *
     * @param uuid String类型主键
     * @return dto
     */
    @Override
    public T selectDtoOneById(String uuid) {
        return JSONObject.parseObject(JSONObject.toJSONString(getById(uuid)),dtoClass);
    }

    /**
     * 根据wrapper查询dto集合
     *
     * @param queryWrapper wrapper
     * @return dto list
     */
    @Override
    public List<T> selectDTOListByWrapper(Wrapper<E> queryWrapper) {
        return JSONObject.parseArray(JSONObject.toJSONString(list(queryWrapper)),dtoClass);
    }

    /**
     * 根据wrapper查询dto
     *
     * @param queryWrapper wrapper
     * @return dto
     */
    @Override
    public T selectDtoOneByWrapper(Wrapper<E> queryWrapper) {
        List<T> list = JSONObject.parseArray(JSONObject.toJSONString(list(queryWrapper)),dtoClass);
        if (CollectionUtils.isEmpty(list)){
            return null;
        }
        Assert.isTrue(list.size()==1,"resultList size != 1");
        return list.get(0);
    }

    /**
     * 分页查询dto
     *
     * @param columnMap 查询map
     * @return Page {@link Page <T>}
     */
    @Override
    public PagedListDto<T> pageListByMap(Map<String, Object> columnMap) {

        int pageIndex = columnMap.get("pageIndex")==null ? 1:Integer.parseInt(columnMap.get("pageIndex").toString());
        int pageSize = columnMap.get("pageSize")==null ? 10:Integer.parseInt(columnMap.get("pageSize").toString());

        Map<String, Object> underLineQueryMap=new HashMap<>();
        getUnderLineMap(columnMap,underLineQueryMap);

        var queryWrapper = Map2WrapperHelper.map2Wrapper(underLineQueryMap,entityClass);
        Page<E> page=Page.of(pageIndex,pageSize);
        page = getBaseMapper().selectPage(page,queryWrapper);

        List<E> entityCollection = page.getRecords();

        List<T> dtoList = JSONObject.parseArray(JSONObject.toJSONString(entityCollection),dtoClass);

        return new PagedListDto(page.getTotal(),pageSize,pageIndex,dtoList);
    }

    /**
     * 分页查询dto
     *
     * @param queryWrapper 查询wrapper
     * @param pageIndex    1 开始
     * @param pageSize     大于0
     * @return Page {@link Page<T>}
     */
    @Override
    public PagedListDto<T> pageListByWrapper(Wrapper<E> queryWrapper, int pageIndex, int pageSize) {

        pageIndex= Math.max(pageIndex, 1);

        Page<E> page=Page.of(pageIndex,pageSize);

        page = getBaseMapper().selectPage(page,queryWrapper);

        List<E> entityCollection = page.getRecords();

        List<T> dtoList = JSONObject.parseArray(JSONObject.toJSONString(entityCollection),dtoClass);

        return new PagedListDto(page.getTotal(),pageSize,pageIndex,dtoList);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveBatch(Collection<E> entityList, int batchSize) {
        String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);
        return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
    }

    /**
     * 获取mapperStatementId
     *
     * @param sqlMethod 方法名
     * @return 命名id
     * @since 3.4.0
     */
    protected String getSqlStatement(SqlMethod sqlMethod) {
        return SqlHelper.getSqlStatement(mapperClass, sqlMethod);
    }

    /**
     * 执行批量操作
     *
     * @param list      数据集合
     * @param batchSize 批量大小
     * @param consumer  执行方法
     * @param <E>       泛型
     * @return 操作结果
     * @since 3.3.1
     */
    protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
        return SqlHelper.executeBatch(this.entityClass, this.log, list, batchSize, consumer);
    }

    /**
     * 执行批量操作（默认批次提交数量{@link IService#DEFAULT_BATCH_SIZE}）
     *
     * @param list     数据集合
     * @param consumer 执行方法
     * @param <E>      泛型
     * @return 操作结果
     * @since 3.3.1
     */
    protected <E> boolean executeBatch(Collection<E> list, BiConsumer<SqlSession, E> consumer) {
        return executeBatch(list, DEFAULT_BATCH_SIZE, consumer);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveOrUpdateBatch(Collection<E> entityList, int batchSize) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
        Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
        String keyProperty = tableInfo.getKeyProperty();
        Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
        return SqlHelper.saveOrUpdateBatch(this.entityClass, this.mapperClass, this.log, entityList, batchSize, (sqlSession, entity) -> {
            Object idVal = tableInfo.getPropertyValue(entity, keyProperty);
            return StringUtils.checkValNull(idVal)
                    || CollectionUtils.isEmpty(sqlSession.selectList(getSqlStatement(SqlMethod.SELECT_BY_ID), entity));
        }, (sqlSession, entity) -> {
            MapperMethod.ParamMap<E> param = new MapperMethod.ParamMap<>();
            param.put(Constants.ENTITY, entity);
            sqlSession.update(getSqlStatement(SqlMethod.UPDATE_BY_ID), param);
        });
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean updateBatchById(Collection<E> entityList, int batchSize) {
        String sqlStatement = getSqlStatement(SqlMethod.UPDATE_BY_ID);
        return executeBatch(entityList, batchSize, (sqlSession, entity) -> {
            MapperMethod.ParamMap<E> param = new MapperMethod.ParamMap<>();
            param.put(Constants.ENTITY, entity);
            sqlSession.update(sqlStatement, param);
        });
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveOrUpdate(E entity) {
        if (null != entity) {
            TableInfo tableInfo = TableInfoHelper.getTableInfo(this.entityClass);
            Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
            String keyProperty = tableInfo.getKeyProperty();
            Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
            Object idVal = tableInfo.getPropertyValue(entity, tableInfo.getKeyProperty());
            return StringUtils.checkValNull(idVal) || Objects.isNull(getById((Serializable) idVal)) ? save(entity) : updateById(entity);
        }
        return false;
    }

    @Override
    public E getOne(Wrapper<E> queryWrapper, boolean throwEx) {
        if (throwEx) {
            return baseMapper.selectOne(queryWrapper);
        }
        return SqlHelper.getObject(log, baseMapper.selectList(queryWrapper));
    }

    @Override
    public Map<String, Object> getMap(Wrapper<E> queryWrapper) {
        return SqlHelper.getObject(log, baseMapper.selectMaps(queryWrapper));
    }

    @Override
    public <V> V getObj(Wrapper<E> queryWrapper, Function<? super Object, V> mapper) {
        return SqlHelper.getObject(log, listObjs(queryWrapper, mapper));
    }

    @Override
    public boolean removeById(Serializable id) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(getEntityClass());
        if (tableInfo.isWithLogicDelete() && tableInfo.isWithUpdateFill()) {
            return removeById(id, true);
        }
        return SqlHelper.retBool(getBaseMapper().deleteById(id));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeByIds(Collection<?> list) {
        if (CollectionUtils.isEmpty(list)) {
            return false;
        }
        TableInfo tableInfo = TableInfoHelper.getTableInfo(getEntityClass());
        if (tableInfo.isWithLogicDelete() && tableInfo.isWithUpdateFill()) {
            return removeBatchByIds(list, true);
        }
        return SqlHelper.retBool(getBaseMapper().deleteBatchIds(list));
    }

    @Override
    public boolean removeById(Serializable id, boolean useFill) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
        if (useFill && tableInfo.isWithLogicDelete()) {
            if (!entityClass.isAssignableFrom(id.getClass())) {
                E instance = tableInfo.newInstance();
                tableInfo.setPropertyValue(instance, tableInfo.getKeyProperty(), id);
                return removeById(instance);
            }
        }
        return SqlHelper.retBool(getBaseMapper().deleteById(id));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeBatchByIds(Collection<?> list, int batchSize) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
        return removeBatchByIds(list, batchSize, tableInfo.isWithLogicDelete() && tableInfo.isWithUpdateFill());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeBatchByIds(Collection<?> list, int batchSize, boolean useFill) {
        String sqlStatement = getSqlStatement(SqlMethod.DELETE_BY_ID);
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
        return executeBatch(list, batchSize, (sqlSession, e) -> {
            if (useFill && tableInfo.isWithLogicDelete()) {
                if (entityClass.isAssignableFrom(e.getClass())) {
                    sqlSession.update(sqlStatement, e);
                } else {
                    E instance = tableInfo.newInstance();
                    tableInfo.setPropertyValue(instance, tableInfo.getKeyProperty(), e);
                    sqlSession.update(sqlStatement, instance);
                }
            } else {
                sqlSession.update(sqlStatement, e);
            }
        });
    }

    @Override
    public BaseMapper<E> getBaseMapper() {
        return baseMapper;
    }

    @Override
    public Class<E> getEntityClass() {
        return entityClass;
    }
}
